funct7
funct7

Reputation: 3601

Swift array messing with the release of weak variables

Swift arrays is that they are value types while classes are reference types, so I thought having something like [SomeClass] would create an array containing the references to SomeClass instances.

However, in Swift REPL, the following happens:

  1> class SomeClass {}
  2> var obj: SomeClass? = SomeClass()
obj: SomeClass? = 0x0000000101100050
  3> weak var weakObj = obj
weakObj: SomeClass? = 0x0000000101100050
  4> var array = [SomeClass?]()
array: [SomeClass?] = 0 values
  5> array.append(obj)
  6> print(obj, weakObj)
Optional(SomeClass) Optional(SomeClass)
  7> array.removeFirst()
$R0: SomeClass? = 0x0000000101100050
  8> obj = nil
  9> print(obj, weakObj)
nil Optional(SomeClass)
 10> print(array)
[]
 11> print(Unmanaged.passUnretained(weakObj!).toOpaque())
0x0000000101100050

I thought the reference count of the instance at 0x0000000101100050 should be 2 after appending obj to array, and once obj = nil and array.removeFirst() were called, both references were removed, therefore the instance should be released.

However, this doesn't seem to be the case. Without the array part, obj is released like it should. What am I missing here?

ADDED It seems like there is something going on with removeFirst(), popLast() and similar functions. (Possibly a bug?)

Setting the object at the array index directly to nil works just fine.

 102> obj = SomeClass()
 103> (weakObj, array) = (obj, [obj])
 104> print(obj, weakObj, array)
Optional(SomeClass) Optional(SomeClass) Optional([Optional(SomeClass)])
 105> obj = nil
 106> array?[0] = nil
$R14: ()? = nil
 107> print(obj, weakObj, array)
nil nil Optional([nil])

However, when using removeLast() or popLast(), weakObj will be released only when array itself is released.

Upvotes: 1

Views: 396

Answers (2)

Alexander
Alexander

Reputation: 63271

Your issue is that array.removeFirst() on line 7 is returning the object that was removed. The repl assigns this to $R0 for you, which is the strong reference keeping your object alive.

Your code behaves as you expect if you explicitly discard the result with _ =:

Welcome to Apple Swift version 3.0.1 (swiftlang-800.0.58.6 clang-800.0.42.1). Type :help for assistance.
  1> class SomeClass {}
  2> var obj: SomeClass? = SomeClass()
obj: SomeClass? = 0x00000001006005b0
  3> weak var weakObj = obj
weakObj: SomeClass? = 0x00000001006005b0
  4> var array = [SomeClass?]()
array: [SomeClass?] = 0 values
  5> array.append(obj)
  6> print(obj, weakObj)
Optional(SomeClass) Optional(SomeClass)
  7> _ = array.removeFirst()
  8> obj = nil
  9> print(obj, weakObj)
nil nil
 10> print(array)
[]
 11> print(Unmanaged.passUnretained(weakObj!).toOpaque())
fatal error: unexpectedly found nil while unwrapping an Optional value
2016-11-27 20:58:42.066831 repl_swift[62143:10516084] fatal error: unexpectedly found nil while unwrapping an Optional value
Current stack trace:
0    libswiftCore.dylib                 0x00000001002bccc0 swift_reportError + 132
1    libswiftCore.dylib                 0x00000001002da070 _swift_stdlib_reportFatalError + 61
2    libswiftCore.dylib                 0x00000001000d00a0 specialized specialized StaticString.withUTF8Buffer<A> ((UnsafeBufferPointer<UInt8>) -> A) -> A + 355
3    libswiftCore.dylib                 0x000000010024c210 partial apply for (_fatalErrorMessage(StaticString, StaticString, StaticString, UInt, flags : UInt32) -> Never).(closure #2) + 109
4    libswiftCore.dylib                 0x00000001000d00a0 specialized specialized StaticString.withUTF8Buffer<A> ((UnsafeBufferPointer<UInt8>) -> A) -> A + 355
5    libswiftCore.dylib                 0x00000001002043d0 specialized _fatalErrorMessage(StaticString, StaticString, StaticString, UInt, flags : UInt32) -> Never + 96
7    repl_swift                         0x0000000100001420 main + 0
8    libdyld.dylib                      0x00007fffaaec7254 start + 1
Execution interrupted. Enter code to recover and continue.
Enter LLDB commands to investigate (type :help for assistance.)
 12>

Upvotes: 1

OOPer
OOPer

Reputation: 47886

Generally speaking, REPL (or Playground) is not a good place to experiment how Swift ARC are working.

Tried your code as a Command Line project of macOS:

import Foundation

class SomeClass {}
var obj: SomeClass? = SomeClass()
weak var weakObj = obj
var array = [SomeClass?]()
array.append(obj)
print(obj, weakObj) //->Optional(SwiftArrayARC.SomeClass) Optional(SwiftArrayARC.SomeClass)
array.removeFirst()
obj = nil
print(obj, weakObj) //->nil nil
print(array) //->[]
print(Unmanaged.passUnretained(weakObj!).toOpaque()) //=>fatal error: unexpectedly found nil while unwrapping an Optional value

Isn't this what you expect?

REPL may keep strong references for the results of each line to enable you to use them later, so, in REPL environment, you cannot get exactly the same behaviour as shown in the actual app.

Create a plain Command Line project and experiment in it. (Caution: LLDB also may keep strong reference. Do not rely on it.)

Upvotes: 2

Related Questions