Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SR-3935] Initializing from @objc protocol with Protocol.Type causes a memory leak #46520

Closed
swift-ci opened this issue Feb 11, 2017 · 7 comments
Assignees
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself memory leak bug: Memory leak

Comments

@swift-ci
Copy link
Collaborator

Previous ID SR-3935
Radar rdar://problem/30478887
Original Reporter gal.orlanczyk (JIRA User)
Type Bug
Status Resolved
Resolution Done
Environment

Version:
Xcode 8.2.1, macOS Sierra 10.12.3
reproducible on simulator and on device iOS 10.2

Additional Detail from JIRA
Votes 1
Component/s Compiler
Labels Bug, Leak
Assignee @slavapestov
Priority Medium

md5: 66af2db86394c6844503ec9fb73ac839

is duplicated by:

  • SR-4133 Memory leak when creating instance using generic type from Objective-C protocol

Issue Description:

Summary:
When initializing an object from protocol.Type with required init the object is leaked even after everything related was released.

Scenario:
I have a protocol named "Plugin"
the protocol has required init with Int param `public required init(param: Int)`

I also have a function:
`func create(plugin: Plugin.Type) -> Plugin`

when I create a plugin using createPlugin(param: ) the instance is initialized and working, but when I look at the memory graph and in instruments I see that the objects created by createPlugin are leaked and has +1 reference count by some unknown factor.
To summarize, an object initialized by protocol.Type gets leaked and an object initialized by a common super class or a non @objc protocol gets initialized just fine.

Steps to reproduce the issue:

1. create the following classes and protocols:
@objc protocol Plugin {
init()
}

@objc class TestPlugin: NSObject, Plugin {
required override init() { }
}
2. in a view controller put the following:
instance property: `var pluginType: Plugin.Type?`
in viewDidLoad or any other view controller life cycle method put this:
`pluginType = TestPlugin.self`
`let plugin = pluginType!.init()`

run the sample project with this declarations and you will see a leaked object right away.
if you will remove the @objc from the protocol you will see there is no issue. something about the bridging between swift -> objc doesn't work here for protocols.

Expected Results:
An Initialized object that work and doesn't have a memory leak

Actual Results:
An initialized object that works BUT has a memory leak

A temporary fix is creating an AbstractPlugin class and receive this class instance.Type instead of the protocols.
Like this:
`class AbstractPlugin: Plugin`
`func create(plugin: AbstractPlugin.Type) -> AbstractPlugin`

OR

creating another protocol to be with @objc for example:
`@objc protocol Plugin`
`protocol MyPlugin: Plugin`
this way I can receive Plugin object and before initializing cast it to MyPlugin which isn't @objc and this way it is created without a leak.

Notes:
The issue only exists for @objc protocols and classes, if the app/framework is for swift only then there is no issue.

To fix this issue on places we must have swift and objc together we need to create a common base class and use BaseClass.Type instead of protocol. this way the object is initialized without a leak.

There is a bug report I opened with this number: 30478887

@belkadan
Copy link
Contributor

@jckarter, you had a dup for this, right?

@jckarter
Copy link
Member

I don't recall it immediately.

@swift-ci
Copy link
Collaborator Author

Comment by Gal Orlanczyk (JIRA)

@jckarter, @belkadan Hey,

Any updates on the issue?

@huonw
Copy link
Mannequin

huonw mannequin commented Mar 22, 2017

More standalone example

import Foundation
@objc protocol Foo {
    init()
}

final class Bar: Foo {
    init() {}

    deinit { print("destroying") }
}

let x: Foo.Type = Bar.self
_ = x.init()
// should print “destroying”, but doesn’t

@tjw
Copy link
Contributor

tjw commented May 19, 2017

I just hit this today as well. One extra note; I thought maybe this had to do with the protocol function being named `init` (and thus part of the +1 ref family in ObjC), but if I make the protocol have a static function with different name, I still get the leak. Here is my test case – only "2" is deinited:

@objc protocol P {
    init(v: Int)
    static func make(v: Int) -> P
}

final class C: P {
    
    let v: Int
    
    init(v: Int) {
        self.v = v
    }
    
    static func make(v: Int) -> P {
        return C(v:v)    
    }
    
    deinit {
        print("\(v)")
    }
}

let type: P.Type = C.self
_ = type.init(v: 1) // deinit is not run for this instance
_ = C(v: 2) // a deinit is run for this instance
_ = type.make(v: 3) // also not deinited, even though it doesn't use `init`

@slavapestov
Copy link
Member

@tjw make() appears to leak because it returns an autoreleased value. However if you wrap the execution in 'autoreleasepool { ... }' it works.

@slavapestov
Copy link
Member

The original bug is valid though, we leak the allocation when calling an init requirement of an @objc protocol.

Fix: #12546

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself memory leak bug: Memory leak
Projects
None yet
Development

No branches or pull requests

5 participants